package fr.esigelec.simulateur.dao.xml;


import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.content.Context;
import android.os.Environment;
import android.util.Log;

import com.google.android.gms.maps.model.LatLng;

import fr.esigelec.simulateur.dao.IVitesseConsigneDAO;
import fr.esigelec.simulateur.dao.sqlite.ConsigneDAO;
import fr.esigelec.simulateur.dto.Consigne;


/**
 * @author Anthony Juanes
 * objectif : class pour manipuler le fichier vitesseConsigne.xml
 * 
 */
public class VitesseConsigneDAO implements IVitesseConsigneDAO {
	
	private final String nomFichierConsigneVitesse = Environment.getExternalStorageDirectory().getPath()+ "/XML/vitesseConsigne.xml";
	private Context context;

	/**
	 * 
	 * Constructeur
	 * objectif : Créé l'instance de VitesseConsigneDAO via un Context
	 * @param context
	 * @return boolean
	 */
	public VitesseConsigneDAO(Context context) {
		super();
		this.context = context;
	}	

	/**
	 * getContext
	 * objectif : Renvoie le Context de la BDD
	 * @return Context
	 */
	public Context getContext() {
		return context;
	}

	/**
	 * setContext
	 * objectif : Set le Context de la BDD
	 * @param context
	 * @return void
	 */
	public void setContext(Context context) {
		this.context = context;
	}
	
	/**
	 * getDocument
	 * objectif : Renvoie un Document à partir d'un chemin d'accès au fichier
	 * @param c
	 * @param nomFichier
	 * @return Document
	 */
	@Override
	public Document getDocument(Context c, String nomFichier) {
		Log.w("TP","VitesseConsigneDAO::récupération d'un document via " + nomFichier);
		try {
			InputStream in = new BufferedInputStream(new FileInputStream(new File(nomFichier)));
			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = builder.parse(in);
			Log.i("TP","VitesseConsigneDAO::Reussite");
			return doc;
		} catch (Exception e) {
			Log.e("TP","VitesseConsigneDAO::Echec de recuperation du document");
			e.printStackTrace();
		}
		Log.e("TP","VitesseConsigneDAO::Echec de recuperation du document");
		return null;
	}	

	/**
	 * getDurationValue
	 * objectif : Renvoie une durée contenue dans un Document (Integer)
	 * @param doc
	 * @return Integer
	 */
	@Override
	public int getDurationValue(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("duration");
		Node node1 = nl1.item(0);
		NodeList nl2 = node1.getChildNodes();
		Node node2 = nl2.item(getNodeIndex(nl2, "value"));
		Log.i("Duration_Value", node2.getTextContent());
		return Integer.parseInt(node2.getTextContent());
	}

	/**
	 * getDistanceValue
	 * objectif : Renvoie une distance contenue dans un Document (Integer)
	 * @param doc
	 * @return Integer
	 */
	@Override
	public int getDistanceValue(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("distance");
		Node node1 = nl1.item(0);
		NodeList nl2 = node1.getChildNodes();
		Node node2 = nl2.item(getNodeIndex(nl2, "value"));
		Log.i("Distance_Value", node2.getTextContent());
		return Integer.parseInt(node2.getTextContent());
	}


	/**
	 * getDurationText
	 * objectif : Renvoie la durée contenue dans un Document (String)
	 * @param doc
	 * @return String
	 */
	@Override
	public String getDurationText(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("duration");
		Node node1 = nl1.item(0);
		NodeList nl2 = node1.getChildNodes();
		Node node2 = nl2.item(getNodeIndex(nl2, "text"));
		Log.i("Duration_Text", node2.getTextContent());
		return node2.getTextContent();
	}

	/**
	 * getDistanceText
	 * objectif : Renvoie une distance contenue par un Document (String)
	 * @param doc
	 * @return String
	 */
	@Override
	public String getDistanceText(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("distance");
		Node node1 = nl1.item(0);
		NodeList nl2 = node1.getChildNodes();
		Node node2 = nl2.item(getNodeIndex(nl2, "text"));
		Log.i("DistanceText", node2.getTextContent());
		return node2.getTextContent();
	}
	
	/**
	 * getStartAddress
	 * objectif : Renvoie l'adresse de départ contenue dans un Document
	 * @param doc
	 * @return String
	 */
	@Override
	public String getStartAddress(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("start_address");
		Node node1 = nl1.item(0);
		Log.i("Start_Address", node1.getTextContent());
		return node1.getTextContent();
	}


	/**
	 * getEndAddress
	 * objectif : Renvoie l'adresse d'arrivée contenue dans un Document
	 * @param doc
	 * @return String
	 */
	@Override
	public String getEndAddress(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("end_address");
		Node node1 = nl1.item(0);
		Log.i("End_Address", node1.getTextContent());
		return node1.getTextContent();
	}

	/**
	 * getStartLocation
	 * objectif : Renvoie les coordonnées de départ contenues dans un Document
	 * @param doc
	 * @return LatLng
	 */
	@Override
	public LatLng getStartLocation(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("start_location");
		Node locationNode = nl1.item(getNodeIndex(nl1, "start_location"));
		nl1 = locationNode.getChildNodes();
		Node latNode = nl1.item(getNodeIndex(nl1, "lat"));
		double lat = Double.parseDouble(latNode.getTextContent());
		Node lngNode = nl1.item(getNodeIndex(nl1, "lng"));
		double lng = Double.parseDouble(lngNode.getTextContent());
		Log.i("Start_Location", locationNode.getTextContent());
		return new LatLng(lat, lng);
	}

	/**
	 * getEndLocation
	 * objectif : Renvoie les coordonnées d'arrivée contenues dans un Document
	 * @param doc
	 * @return LatLng
	 */
	@Override
	public LatLng getEndLocation(Document doc) {
		NodeList nl1 = doc.getElementsByTagName("end_location");
		Node locationNode = nl1.item(getNodeIndex(nl1, "end_location"));
		nl1 = locationNode.getChildNodes();
		Node latNode = nl1.item(getNodeIndex(nl1, "lat"));
		double lat = Double.parseDouble(latNode.getTextContent());
		Node lngNode = nl1.item(getNodeIndex(nl1, "lng"));
		double lng = Double.parseDouble(lngNode.getTextContent());
		Log.i("End_Location", locationNode.getTextContent());
		return new LatLng(lat, lng);
	}	

	/**
	 * getNodeIndex
	 * objectif : Renvoie l'index de la node spécifiée
	 * @param nl
	 * @param nodename
	 * @return Integer
	 */
	private int getNodeIndex(NodeList nl, String nodename) {
		for (int i = 0; i < nl.getLength(); i++) {
			if (nl.item(i).getNodeName().equals(nodename))
				return i;
		}
		return -1;
	}

	/**
	 * decodePoly
	 * objectif : Renvoie la liste des coordonnées contenues dans une String
	 * @param encoded
	 * @return poly
	 */
	private List<LatLng> decodePoly(String encoded) {
		List<LatLng> poly = new ArrayList<LatLng>();
		int index = 0, len = encoded.length();
		int lat = 0, lng = 0;
		while (index < len) {
			int b, shift = 0, result = 0;
			do {
				b = encoded.charAt(index++) - 63;
				result |= (b & 0x1f) << shift;
				shift += 5;
			} while (b >= 0x20);
			int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
			lat += dlat;
			shift = 0;
			result = 0;
			do {
				b = encoded.charAt(index++) - 63;
				result |= (b & 0x1f) << shift;
				shift += 5;
			} while (b >= 0x20);
			int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
			lng += dlng;
			LatLng position = new LatLng(lat / 1E5, lng / 1E5);
			poly.add(position);
		}
		return poly;
	}

	/**
	 * getElevation
	 * objectif : Renvoie une liste d'elevation contenues dans un document	 * 
	 * @param doc
	 * @return list
	 */
	@Override
	public List<Double> getElevation(Document doc) {
		Log.w("TP", "ItineraireEnrichiDAO:: Récupération de l'élevation");
		NodeList nl1, nl2, nl3;
		List<Double> list = new ArrayList<Double>();
		nl1 = doc.getElementsByTagName("step");
		if (nl1.getLength() > 0) {
			for (int i = 0; i < nl1.getLength(); i++) {
				Node node1 = nl1.item(i);
				nl2 = node1.getChildNodes();
				Node locationNode = nl2.item(getNodeIndex(nl2, "start_location"));
				nl3 = locationNode.getChildNodes();
				Node elevationNode = nl3.item(getNodeIndex(nl3, "elevation"));
				double elevation = Double.parseDouble(elevationNode.getTextContent());
				list.add(elevation);
				locationNode = nl2.item(getNodeIndex(nl2, "end_location"));
				nl3 = locationNode.getChildNodes();
				elevationNode = nl3.item(getNodeIndex(nl3, "elevation"));
				elevation = Double.parseDouble(elevationNode.getTextContent());
				list.add(elevation);
			}
		}
		return list;
	}
	
	/**
	 * getAngle 
	 * objectif : obtenir une liste d'angle à partir du fichier vitesseConsigne.xml
	 * @param doc
	 * @return list
	 */	
    @Override
	public List<Double> getAngle(Document doc) {
        NodeList nl1, nl2, nl3;
        List<Double> list = new ArrayList<Double>();
        nl1 = doc.getElementsByTagName("step");
        if(nl1.getLength() > 0) {
            for(int i = 0; i < nl1.getLength(); i++) {
                Node node1 = nl1.item(i);
                nl2 = node1.getChildNodes();
                Node locationNode = nl2.item(getNodeIndex(nl2, "start_location"));
                nl3 = locationNode.getChildNodes();
                Node angleNode = nl3.item(getNodeIndex(nl3, "angle"));
                double angle = Double.parseDouble(angleNode.getTextContent());                
                list.add(angle);                
                locationNode = nl2.item(getNodeIndex(nl2, "end_location"));
                nl3 = locationNode.getChildNodes();
                angleNode = nl3.item(getNodeIndex(nl3, "angle"));
                angle = Double.parseDouble(angleNode.getTextContent());
                list.add(angle); 
            }
        }
        return list;
    } 
	
	/**
	 * getListeVitesseBDD
	 * objectif : Renvoie la liste des Vitesse en fonction d'une liste d'angles en cherchant la correspondance dans la BDD
	 * @param listAngle
	 * @return listVitesse
	 */
	@Override
	public List<Consigne> getListVitesseBDD(List<Double> listAngle) {
		Log.i("TP","VitesseConsigneDAO::Recuperation de la liste des vitesses de consigne");
		List<Consigne> listVitesse = new ArrayList<Consigne>();
		boolean test = false;
		// récupération de toutes les consignes existantes dans la BDD
		Log.i("TP", "VitesseConsigneDAO::Lecture dans la BDD");

		List<Consigne> consigneVitesses = new ConsigneDAO(this.context).getConsignesBDD("angle ASC");
		// variable temporaire de comparaison, initialisée au premier élément de
		// la table Consigne
		Consigne temp = consigneVitesses.get(0);

		// exploration de la liste des angles
		for (double angle : listAngle) {
			Log.e("TP", "VitesseConsigneDAO::Test pour l'Angle : " + angle);
			// comparaison dans la liste de consigne des angles
			for (Consigne consigne : consigneVitesses) {
				Log.i("TP", "VitesseConsigneDAO::Test pour " + consigne.getAngle());
				if (angle == consigne.getAngle()) {
					Log.i("TP", "VitesseConsigneDAO::Angle trouve !");
					// ajout de la vitesse correspondante (pour le premier angle
					// trouvé)
					listVitesse.add(consigne);
					Log.i("TP", "VitesseConsigneDAO::Vitesse ajoutee : " + consigne.getVitesse());
					test = true;
					break;
				} else if (angle > temp.getAngle()
						&& angle < consigne.getAngle()) {
					Log.w("TP", "VitesseConsigneDAO::Angle comprit entre " + temp.getAngle() + " et " + consigne.getAngle());
					// si l'angle à comparer a dépassé celui de la consigne,
					// récupération du précédent
					listVitesse.add(temp);
					Log.i("TP", "VitesseConsigneDAO::Vitesse ajoutee : " + temp.getVitesse());
					test = true;
					break;
				} else {
					Log.e("TP", "VitesseConsigneDAO::Aucune correspondance");
					// stockage de la donnée pour comparaison
					temp = consigne;
				}
			}
			Log.w("TP", "VitesseConsigneDAO::Fin de recherche de Consigne pour " + angle);
			if (test == false) {
				// si aucun angle trouvé dans la table Consigne, mise à -1 pour
				// Erreur (aucune vitesse dispo)
				Log.e("TP", "VitesseConsigneDAO::Aucune Consigne trouvee");
				listVitesse.add(new Consigne(0,0,0));
			} else if (test == true) {
				// si un angle a été trouvé, remise à false
				Log.w("TP", "VitesseConsigneDAO::RAZ");
				test = false;
			}
		}
		return listVitesse;
	}

	/**
	 * getVitesse
	 * objectif : Renvoie la liste des vitesses contenues dans un document
	 * @param doc
	 * @return list
	 */
	@Override
	public List<Integer> getListVitesseXML(Document doc) {
		NodeList nl1, nl2, nl3;
		List<Integer> list = new ArrayList<Integer>();
		nl1 = doc.getElementsByTagName("step");
		if (nl1.getLength() > 0) {
			for (int i = 0; i < nl1.getLength(); i++) {
				Node node1 = nl1.item(i);
				nl2 = node1.getChildNodes();
				Node locationNode = nl2.item(getNodeIndex(nl2, "start_location"));
				nl3 = locationNode.getChildNodes();
				Node vitesseNode = nl3.item(getNodeIndex(nl3, "vitesse"));
				int vitesse = Integer.parseInt(vitesseNode.getTextContent());
				list.add(vitesse);
				locationNode = nl2.item(getNodeIndex(nl2, "end_location"));
				nl3 = locationNode.getChildNodes();
				vitesseNode = nl3.item(getNodeIndex(nl3, "vitesse"));
				vitesse = Integer.parseInt(vitesseNode.getTextContent());
				list.add(vitesse);
			}
		}
		return list;
	}
	
	/**
	 * getLatLng
	 * objectif : Renvoie la liste des coordonnées d'un itineraire contenu par un document
	 * @param doc
	 * @return list
	 */
	@Override
	public List<LatLng> getLatLng(Document doc) {
		Log.w("TP", "ItineraireEnrichiDAO::Récupération des LatLng");
		NodeList nl1, nl2, nl3;
		List<LatLng> list = new ArrayList<LatLng>();
		nl1 = doc.getElementsByTagName("step");
		if (nl1.getLength() > 0) {
			for (int i = 0; i < nl1.getLength(); i++) {
				Node node1 = nl1.item(i);
				nl2 = node1.getChildNodes();

				Node locationNode = nl2
						.item(getNodeIndex(nl2, "start_location"));
				nl3 = locationNode.getChildNodes();
				Node latNode = nl3.item(getNodeIndex(nl3, "lat"));
				double lat = Double.parseDouble(latNode.getTextContent());
				Node lngNode = nl3.item(getNodeIndex(nl3, "lng"));
				double lng = Double.parseDouble(lngNode.getTextContent());
				list.add(new LatLng(lat, lng));

				locationNode = nl2.item(getNodeIndex(nl2, "polyline"));
				nl3 = locationNode.getChildNodes();
				latNode = nl3.item(getNodeIndex(nl3, "points"));
				List<LatLng> arr = decodePoly(latNode.getTextContent());
				for (int j = 0; j < arr.size(); j++) {
					list.add(new LatLng(arr.get(j).latitude, arr.get(j).longitude));
				}

				locationNode = nl2.item(getNodeIndex(nl2, "end_location"));
				nl3 = locationNode.getChildNodes();
				latNode = nl3.item(getNodeIndex(nl3, "lat"));
				lat = Double.parseDouble(latNode.getTextContent());
				lngNode = nl3.item(getNodeIndex(nl3, "lng"));
				lng = Double.parseDouble(lngNode.getTextContent());
				list.add(new LatLng(lat, lng));
			}
		}
		return list;
	}
	
	/**
	 * addVitesse 
	 * objectif : ajouter un element vitesse à partir du fichier itineraireEnrichi.xml et création du fichier vitesseConsigne.xml
	 * @param doc
	 * @return listVitesses
	 */
	@Override
	public List<Consigne> addVitesse(Document doc) {
		Log.w("TP", "ItineraireEnrichiDAO::Ajout de vitesse");
		List<Double> listAngles = this.getAngle(doc);
		Log.i("TP", "ItineraireEnrichiDAO::Nombre d'Angles récupérés : " + listAngles.size());		
		List<Consigne> listVitesses = new VitesseConsigneDAO(this.context).getListVitesseBDD(listAngles);
		Log.i("TP", "ItineraireEnrichiDAO::Nombre de Vitesses récupérés : " + listVitesses.size());
				
		try {
			NodeList startList = doc.getElementsByTagName("start_location");
			NodeList endList = doc.getElementsByTagName("end_location");
			for (int i = 0; i < (startList.getLength() + endList.getLength()) - 2; i++) {
				// creation d'un element vitesse
				Element vitesse = doc.createElement("vitesse");
				Log.i("TP", "ItineraireEnrichiDAO::Node " + i);
				vitesse.appendChild(doc.createTextNode(String.valueOf(((int)(listVitesses.get(i).getVitesse())))));
				if (i % 2 == 0)
					startList.item((int) Math.floor(i / 2.0)).appendChild(vitesse);
				else if (i % 2 != 0)
					endList.item((int) Math.floor(i / 2.0)).appendChild(vitesse);
			}
			// écriture dans le fichier itineraireEnrichi.xml
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer t = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new StringWriter());
			t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","2");
			t.setOutputProperty(OutputKeys.INDENT, "yes");
			t.transform(source, result);
			Writer docI = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(nomFichierConsigneVitesse), "UTF-8"));
			String output = result.getWriter().toString();
			docI.write(output);
			docI.close();
			return listVitesses;
		} catch (Exception ex) {
			Log.e("TP", "ItineraireEnrichiDAO::Erreur: " + ex.getMessage());
			return null;
		}
	}

	/**
	 * deleteFile
	 * objectif : supprimer un fichier 
	 * @param nomFichier
	 * @return boolean
	 */
	@Override
	public boolean deleteFile(String nomFichier) {
		Log.w("TP", "ItineraireEnrichiDAO::Suppression du fichier " + nomFichier);
		File file = new File(nomFichier);
		Log.w("TP", "ItineraireEnrichiDAO::Fait.");
		return file.delete();
	}
}

